
require('luapl')
require('luaunit')

-- https://github.com/stevedonovan/Penlight/blob/master/lua/pl/tablex.lua
--- compare two values.
-- if they are tables, then compare their keys and fields recursively.
-- @param t1 A value
-- @param t2 A value
-- @param ignore_mt if true, ignore __eq metamethod (default false)
-- @return true or false
function compare(t1,t2,ignore_mt)
    local ty1 = type(t1)
    local ty2 = type(t2)
    if ty1 ~= ty2 then return false end
    -- non-table types can be directly compared
    if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end
    -- as well as tables which have the metamethod __eq
    local mt = getmetatable(t1)
    if not ignore_mt and mt and mt.__eq then return t1 == t2 end
    for k1,v1 in pairs(t1) do
        local v2 = t2[k1]
        if v2 == nil or not compare(v1,v2,ignore_mt) then return false end
    end
    for k2,v2 in pairs(t2) do
        local v1 = t1[k2]
        if v1 == nil or not compare(v1,v2,ignore_mt) then return false end
    end
    return true
end

-- Redefine luaunit assert method to compare A objects and tables
-- Usage:
-- assert( boolean )
-- assert( A, { table } )
do
  local luassert = assert
  assert = function (x,y)
    if y == nil then luassert(x); return end
    luassert(compare(x:value(),y))
  end
end

------------------------------------------------------------------------------
-- Tests
------------------------------------------------------------------------------

TestluAPL = {}
  function TestluAPL:tearDown()
  -- force a garbage collection
    collectgarbage()
  end

  function TestluAPL:testtil()
    local x =A(til,6)
    assert(x,{0,1,2,3,4,5})
    assert(tostring(x) == " 0 1 2 3 4 5")
    assert(x:type() == "INT")
    x = A(til,6,3)
    assert(tostring(x) == "  0  1  2\n  3  4  5\n  6  7  8\n  9 10 11\n 12 13 14\n 15 16 17")
  end

  function TestluAPL:testAA()
    local x = AA(1,AA(11,AA(111,222,333),33),2,3)
    assert(x, { 1, {11, {111,222,333}, 33}, 2,3})
  end

  function TestluAPL:testpick()
  -- y pick x - pick is a choose followed by a disclose
    local x = AA(1,2,3)
    local y = A(1,pick,x)
    assert(y:value() == 2)
    y = A(0,pick,x)
    assert(y:value() == 1)
    y = A(2,pick,x)
    assert(y:value() == 3)
    y = A(0,1,pick,AA(AA("abcd",A(3,4,1)),S("xyz")))
    assert(y,{3,4,1})
  end

  function TestluAPL:testLV()
    local x = AA(1,2,3)
    LV(2,pick,x):assign(10)
    assert(x, {1,2,10})
    local y = AA(11,AA(111,222,333),33)
    local z = A(1,pick,y)
    LV(1,pick,z):assign(2)
    assert(y, {11, {111,222,333}, 33})
    assert(z, {111,2,333})
    x = A(til,10)
    LV(2,choose,x):assign(100)
    assert(x, {0,1,100,3,4,5,6,7,8,9} )
    x = A("abcdefg")
    LV(2,choose,x):assign("X")
    assert(x:tostring()=="abXdefg")
  end

  function TestluAPL:testcount0()
    local x = AA(1,2,3,4)
    local y = A(count,x)
    assert(y:value()==4)
  end

  function TestluAPL:testcount()
    local x = A(1,2,3)
    local y = A(count,x)
    assert(y:value()==3)
    assert(#x==3)
    x = A(1)
    y = A(count,x)
    assert(y:value()==1)
    assert(#x==1)
  end

  function TestluAPL:testeach()
    local x = AA(A(1,1,2,3,3),"abcd")
    assert(A(uniq,each,x),{{1,2,3},"abcd"})
    local xx = AA(x,x)
    assert(A(uniq,each,each,xx),{{{1,2,3},"abcd"},{{1,2,3},"abcd"}})
    local y = AA(11,AA(111,222,333),33)
    local z = A(count,each,y)
    assert(z,{1,3,1})
  end

  function xtest3(x) local a = A(til,x) return a end
  function xtest2(x) local a = A(xtest3,x) return a end
  function xtest1(x) local a = A(xtest2,x) return a end
  function TestluAPL:testfncall()
    local x = A(xtest1,10)
    print(x)
  end

  function xtest4(x,y) return A(x,pick,y) end
  function TestluAPL:testdyadic()
    local x = A(1,xtest4,AA(11,22,33))
    print(x)
  end

  function TestluAPL:testplus()
    local x = A(11,plus,23)
    assert(x:value()==34)
    x = A(11,plus,23,plus,16)
    assert(x:value()==50)
    x = A(1) + A(1)
    assert(x:value()==2)
    x = 1 + A(3)
    assert(x:value()==4)
    x = A(19) + 23
    assert(x:value()==42)
    x = 1 + A(1,2,3,4)
    assert(x,{2,3,4,5})
    x = A(2,3,4) + A(3,4,5)
    assert(x,{5,7,9})
  end

  function TestluAPL:testminus()
    local x = A(11,minus,23)
    assert(x:value()==-12)
    x = A(11,minus,23,minus,16)
    assert(x:value()==4)
    x = A(1) - A(1)
    assert(x:value()==0)
    x = 1 - A(3)
    assert(x:value()==-2)
    x = A(23) - 19
    assert(x:value()==4)
    x = 1 - A(1,2,3,4)
    assert(x,{0,-1,-2,-3})
    x = A(3,4,5) - A(1,2,3)
    assert(x,{2,2,2})
  end


  function TestluAPL:testmultiply()
    local x = A(11,mult,23)
    assert(x:value()==253)
    x = A(11,mult,23,mult,16)
    assert(x:value()==11*23*16)
    x = A(1) * A(1)
    assert(x:value()==1)
    x = 3 * A(3)
    assert(x:value()==9)
    x = A(19) * 23
    assert(x:value()==19*23)
    x = 2 * A(1,2,3,4)
    assert(x,{2,4,6,8})
    x = A(2,3,4) * A(3,4,5)
    assert(x,{6,12,20})
  end

  function TestluAPL:testdivide()
    local x = A(11,div,23)
    assert(x:value()==11/23)
    x = A(11,div,23,div,16)
    assert(11/(23/16)==x:value())
    x = A(1) / A(1)
    assert(x:value()==1)
    x = 1 / A(3)
    assert(x:value()==1/3)
    x = A(23) / 19
    assert(x:value()==23/19)
    x = 1 / A(1,2,3,4)
    assert(x,{1,1/2,1/3,1/4})
    x = A(3,4,5) / A(1,2,3)
    assert(x,{3,2,5/3})
  end

  function TestluAPL:testequals()
    local x = A(11,equals,23)
    assert(x:value()==0)
    x = A(1,equals,23,equals,23)
    assert(x:value()==1)
    x = A(A(3,2,5),equals,A(1,2,3))
    assert(x,{0,1,0})
  end

  function TestluAPL:testmatch()
    local x = A(1) == A(1)
    assert(x)
    x = A(1) == A(2)
    assert(not x)
    x = A(1,2,3) == A(1,2,3)
    assert(x)
    x = A(1,2,3) == A(1,8,3)
    assert(not x)
    x = A(A(1,2,3),match,A(1,2,3))
    assert(x:value()==1)
    x = A(A(1,2,3),match,A(1,4,3))
    assert(x:value()==0)
  end

  function TestluAPL:testlaminate()
    local x = A(A(1),laminate,A(2))
    assert(x,{1,2})
  end

  function TestluAPL:testsum()
    local x = A(sum,til,11)
    assert(x:value()==55)
  end

  function TestluAPL:testuniq()
    local x = A(1,1,2,3,4,1,5,6,5)
    local y = uniq(x)
    assert(y,{1,2,3,4,5,6})
  end

  function TestluAPL:testtostring()
    local x = A(1,2,3)
    assert(tostring(x) == " 1 2 3")
    x = A(1.1,plus,1,2,3)
    assert(tostring(x) == " 2.1 3.1 4.1")
    x = AA(11,AA(11,22),11)
    assert(tostring(x) == "<  11\n< <  11\n  <  22\n<  11")
    x = AA(11,A(2,2,2,shape,100,2,300,-4000),22);
    assert(tostring(x) == "<  11\n<    100     2\n     300 -4000\n  \n     100     2\n     300 -4000\n<  22")
  end

  function TestluAPL:testshape()
    local x = A(shape,til,9)
    assert(x:value() == 9)
    x = A(3,3,shape,1,plus,til,9)
    assert(x,{ {1,2,3}, {4,5,6}, {7,8,9} })
    assert(tostring(x) == " 1 2 3\n 4 5 6\n 7 8 9")
    x = A(2,2,2,shape,100,2,300,-4000)
    assert(x,{ {{100,2},{300,-4000}}, {{100,2},{300,-4000}} })
    x = AA(111,A(2,2,2,2,shape,100,2,300,-4000),333)
    assert(x,{ 111, { { { {100,2},{300,-4000} }, { {100,2},{300,-4000} } }, { { {100,2},{300,-4000} }, { {100,2},{300,-4000} } } }, 333})
    assert(tostring(x) == "<  111\n<    100     2\n     300 -4000\n  \n     100     2\n     300 -4000\n  \n  \n     100     2\n     300 -4000\n  \n     100     2\n     300 -4000\n<  333")
    x = A(2,3,shape,"abcdef")
    assert(tostring(x) == "abc\ndef")
    assert(x,{ "abc", "def" })
  end

  function TestluAPL:testsymbol()
    local x = S("abc","123")
    assert(#x==2)
    assert(tostring(x) == " `abc `123")
  end

  function TestluAPL:testbrackets()
    local x = A(til,19)
    assert(x[3]:value()==3)
    x[3] = 100
    assert(x[3]:value()==100)
    print(A(sum,x,minus,til,19))
    assert(A(sum,x,minus,til,19):value()==97)

    x=A(til,5,3,3)
    print(x)
    assert(x[AA(3,2,1)]:value()==34)
    x[AA(3,2,1)] = 1
    print(x)
    assert(x[AA(3,2,1)]:value()==1)
  end

  function TestluAPL:testslotfiller()
    local x = A(S("a","b","c"),AA(A(1,2,3),"testing",A(til,3,3)))
    print(x)
    print(A(S("a"),pick,x))
    LV(S("a"),pick,x):assign(S("newval"))
    print(x)
  end

  function TestluAPL:testenclose()
  -- enclose x -- create a scalar aobj that contains x
    local x = A(enclose,1,2,3)
    print(x)
  end

  function TestluAPL:testatype()
  -- type x -- returns "int","float","char","null","box","sym","func", "unknown"
    local x = A(_type,1,2,3)
    assert(tostring(x)==" `int")
  end

  function TestluAPL:testrand()
  -- y rand x        -- "deal" y unique integers from til x (requires y<x)
  -- rand y shape x  -- "roll" an x sided dice y times
    local x = A(10,rand,10)
    assert(#x == 10)
    assert(A(sum,x):value()==45)
    x = A(rand,5000,shape,1000)
    assert(#x==5000)
    assert(#uniq(x)<=5000)
  end

  function TestluAPL:testchoose()
  -- y choose x - alternative syntax for x[ ... ]
    local x = A(2,0,choose,3,3,shape,"abcdefghi")
    assert(tostring(x)=="ghi\nabc")
  end

  function TestluAPL:testdepth()
  -- depth x - returns the maximum depth of nesting in x
    local x = A(depth,AA(1,AA(1,2),2))
    assert(x:value()==2)
    x = A(depth,enclose,enclose,enclose,"abcde")
    assert(x:value()==3)
  end

  function TestluAPL:testrot()
  -- y rot x - rotate x by y
    local x = A(0,-2,1,rot,til,5,3)
    print(x)
  end

  function TestluAPL:testreverse()
  -- reverse x
    local x = A(reverse,1,2,3,4)
    assert(x,{4,3,2,1})
  end

  function TestluAPL:testdisclose()
  -- disclose x - removes one level of nesting
    local x = A(disclose,AA(A(til,3),A(10,mult,til,3)))
    assert(tostring(x)=="  0  1  2\n  0 10 20")
  end

  function TestluAPL:testalsf()
  -- association list to slotfiller
  -- Turns an alternating tuple (symbol, value, symbol, value, ...) into a slotfiller
    local x = AA(S("a"),1,S("b"),2,S("c"),3)
    local y = alsf(x)
    assert(y, {a=1, b=2, c=3})
  end

  function TestluAPL:testissf()
  -- is slotfiller 
  -- returns true if the a object is a slotfiller
    local x = AA(S("a"),1,S("b"),2,S("c"),3)
    local y = alsf(x)
    assert(issf(y)==1)
    assert(issf(x)==0)
  end

  function TestluAPL:testflat()
  -- flattens a data structure
    local x = AA("ab",A(2,2,shape,"abcd"),"cd")
    local y = flat(x)
    assert(y:value()=="ababcdcd")
  end

  function TestluAPL:testbag()
    local x = A("Bag can be used to split up arrays")
    local bv = A(x,equals," ")
    assert(A(bag,1,concat,bv),{4,4,3,5,3,6,3,7})
    local y = A(A(bag,1,concat,bv),bag,x)
    assert(y, {"Bag ", "can ", "be ", "used ", "to ", "split ", "up ", "arrays"})
  end

  function TestluAPL:testconcat()
    local x = A(1,2,3,4)
    local y = A(5,6,7)
    assert(A(x,concat,y),{1,2,3,4,5,6,7})
  end

  function TestluAPL:testdrop()
    local x = A(1,2,3,4)
    assert(A(1,drop,x),{2,3,4})
    assert(A(-1,drop,x),{1,2,3})
  end

  function TestluAPL:testin()
    local x = A(1,_in,1,2,3)
    assert(x:value()==1)
  end

  function TestluAPL:testrake()
    local x = AA(1,2,AA(1,2,3),3,4)
    print(A(rake,x))
  end

  function TestluAPL:testraze()
    local x = AA(A(til,2,3),A(10,plus,til,2,3))
    print(A(raze,x))
  end

  function TestluAPL:testrestructure()
    local x = A(3,restructure,"abcdef")
    assert(x,{"abc","def"})
    x = A(3,restructure,AA("abc","def","123","456","ABC","DEF"))
    assert(x,{ {"abc","def","123"}, {"456","ABC","DEF"} })
    x = A(sum,rank,1,right,-3,restructure,0,1,2,3,4,5,6)
    assert(x,{3,6,9,12,15})
  end

  function TestluAPL:testright()
  -- returns the right argument
    local x = A(2,4,8,concat,rank,0,right,0.25,0.75,1.25)
    assert(x,{ {2,0.25}, {4,0.75}, {8,1.25} })
  end

  function TestluAPL:testsolve()
  -- matrix inversion (monadic)
  -- linear equation solver (dyadic)
    local x = A(10,-3,5,solve,3,3,shape,3,5,-1,7,-2,4,1,1,2)
    local res = A(-0.5180722892,2.602409639,1.457831325)
    assert(A(abs,sum,x,plus,neg,res):value()<0.000000001)
  end

  function TestluAPL:testpack()
  -- Convert string to symbol
    local x = A(raze,pack,each,AA("abc","def"))
    assert(tostring(x)==" `abc `def")
    x = A(pack,2,3,shape,"abcdef")
    assert(tostring(x)==" `abc `def")
  end

  function TestluAPL:testunpack()
  -- Convert symbol to string
    local x = A(_unpack,S("abc","def"))
    assert(x,{ "abc", "def" })
  end

  function TestluAPL:testtranspose()
    local x = A(1,1,0,transpose,til,2,3,4)
    print(x)
  end

  function TestluAPL:testtable2aobject()
    local x = A({1,2,3,4})
    assert(x,{1,2,3,4})
    x = A({1, {2,3}, 4})
    assert(x,{1,{2,3},4})
    x = A({1,2,3,4},plus,{1,2,3,4})
    assert(x,{2,4,6,8})
    x = A({a=1,b=2,c=3})
    assert(x == AA(S("a","c","b"),AA(1,3,2)))
    x = A({a=1,b=2,c={1,2,4,8}})
    assert(x == AA(S("a","c","b"),AA(1,{1,2,4,8},2)))
    x = A({apple=1.0, b=2, c={1,2,3.2,4}})
    assert(x==alsf(AA(S("apple"),1.0,S("c"),{1,2,3.2,4},S("b"),2)))
  end

  function TestluAPL:testgradeup()
    local x = A(_unpack,S("apl", "a", "apple"))
    local y = A(A(gradeup,x),choose,x)
    assert(y,{ "a    ", "apl  ", "apple"})
  end

  function TestluAPL:testgradedown()
    local x = A(_unpack,S("apl", "a", "apple"))
    local y = A(A(gradedown,x),choose,x)
    assert(y,{ "apple", "apl  ", "a    "})
  end

  function TestluAPL:testtake()
    local x = A(3,take,til,5,2)
    assert(x,{ {0,1},{2,3},{4,5} })
    local y = A(-3,take,til,5,2)
    assert(y,{ {4,5},{6,7},{8,9} })
  end

  function TestluAPL:testdecode()
    local x = A(24,60,60,decode,2,5,59)
    assert(x:value()==7559)
  end

  function TestluAPL:testbins()
    local v = A(rand,5000,shape,1000)
    local x = A(enclose,A(100,mult,1,plus,til,10),bins,v)
    local z = A(pick,sum,each,x,equals,each,til,10)
    assert(A(sum,z):value()==5000)
  end

  function TestluAPL:testsf2table()
    local x = AA(S("a"),1,S("b"),2,S("c"),3)
    local y = alsf(x)
    assert(y,{ a=1, b=2, c=3 })
  end

  function TestluAPL:testnestedarrays()
    local x = A(rand,each,A(10,shape,50),A(15,shape,32))
    print(x)
    x = A(til,each,3,5,10,6)
    print(x)
    x = A(10,rand,each,15,20,25)
    print(x)
    x = A(10,15,rand,each,15,20)
    print(x)
    x = A(10,15,rand,each,25)
    print(x)
    x = A(enclose,rank,1,til,5,7)
    print(x)
    --
    local v = AA("abc",A(til,5,2),S("as_de"),A(12,34,891))
    local a = A(2,2,shape,v)
    print(a[AA(0,1)])
    print(A(AA(0,1),choose,a))
    print(A(shape,AA(0,1),choose,a))
  end
  
  function TestluAPL:testexport()
  -- export an A object to a binary string value
    local x = A(1,2,3)
    local y = luapl.export(x)
    assert(A(count,y):value()==28)
    local z = luapl.import(y)
    print(x)
    print(z)
    assert(x == z)
  end

  function TestluAPL:testinner()
  -- matrix inner product
    local x = A(2,2,shape,1,2,3,4)
    local y = A(2,2,shape,4,3,2,1)
    local z = A(x,inner,y)
    assert(A(shape,z),{2,2})
    assert(z,{{8,5}, {20,13}})
  end  

  function TestluAPL:testinnermax()
  -- innermax matrix product R = A max.+ B
  -- R(i,j) = max element of ( row i of A +  col j of B)
    local x = A(2,2,shape,1,2,3,4)
    local y = A(2,2,shape,4,3,2,1)
    local z = A(x,innermax,y)
    assert(A(shape,z),{2,2})
    assert(z,{{5,4}, {7,6}})
  end

  function TestluAPL:testinnermin()
  -- innermin matrix product R = A min.+ B
  -- R(i,j) = min element of ( row i of A +  col j of B)
    local x = A(2,2,shape,1,2,3,4)
    local y = A(2,2,shape,4,3,2,1)
    local z = A(x,innermin,y)
    assert(A(shape,z),{2,2})
    assert(z,{{4,3}, {6,5}})
  end
  
  function TestluAPL:testprime()
  -- generate prime numbers less than R
    local R = 100
    local x = 1+A(1,drop,til,R)
    local res = A(A(_not,x,_in,ravel,x,outermult,x),compress,x)
    assert(res,{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 })
  end

  function TestluAPL:testshuffle()
  -- shuffle array elements
    local x = A(til,10)
    assert(x[A(gradeup,gradedown,A(shape,x),shape,0,1)], {5,0,6,1,7,2,8,3,9,4})
  end

  function TestluAPL:testssr()
  -- string search and replace
    local x = "the quick brown fax jumped over the lazy dog"
    assert(luapl.ssr(x,"fax","fox"):value()=="the quick brown fox jumped over the lazy dog")
  end
  
  function TestluAPL:testss()
  -- string search
    local x = "mississippi"
    assert(luapl.ss(x,"ss"),{2,5})
  end

  function TestluAPL:testmod()
  -- modulo
    local x = A(5,mod,7)
    assert(x:value()==2)
  end

  function TestluAPL:testor()
  -- or
    local x = A(1,0,0,1)
    local y = A(0,1,1,1)
    assert(A(x,_or,y),{1,1,1,1})
  end

  function TestluAPL:testand()
  -- and
    local x = A(1,0,0,1)
    local y = A(0,1,1,1)
    assert(A(x,_and,y),{0,0,0,1})
  end

  function TestluAPL:testpe1()
  -- project euler problem 1
  -- If we list all the natural numbers below 10 that are multiples of 3 or 5,
  -- we get 3, 5, 6 and 9. The sum of these multiples is 23.
  -- Find the sum of all the multiples of 3 or 5 below 1000.
  --
    local x = A(til,1000)             -- numbers 0, 1, ... 999
    local mod3 = A(_not,3,mod,x)      -- binary vector 1 if divisible by 3
    local mod5 = A(_not,5,mod,x)      -- binary vector 1 if divisible by 5
    local bv = A(mod3,_or, mod5)      -- binary vector 1 if divisible by 3 or 5
    local res = A(sum,bv,compress,x)  -- sum the items in x that are flagged by bv
    assert(res:value()==233168)
  end

  function TestluAPL:testpow()
  -- x power y - raise x to the power y
    local x = A(5,pow,3)
    assert(math.abs(x:value()-125)<0.000000001)
    local y = x^2
    print(y)
    assert(math.abs(y:value()-125*125)<0.000000001)
  end

  function TestluAPL:testexp()
  -- exp{x} - return e (2.71828...) to the power x
    local x = A(exp,-1,0,1,2,710)
    assert(x:tostring()==" 0.3678794412 1 2.718281828 7.389056099 Inf")
  end

  function TestluAPL:testpi()
  -- pi{x} - return pi (3.14159...) to the power x
    local x = A(pi,1, 2, 0.5, 1e308)
    assert(x:tostring()==" 3.141592654 6.283185307 1.570796327 Inf")
  end

  function TestluAPL:testlog()
  -- y log x - return log x in base y
  -- log x - return ln x the natural log (base e)
    local x = A(10, log, 0.1, 1, 10, 100, 1000, 1234.5, 0)
    assert(x:tostring()==" -1 0 1 2 3 3.091491094 -Inf")
    local y = A(log, 1, 10, 100, 0)
    assert(y:tostring()==" 0 2.302585093 4.605170186 -Inf")
  end

  function arraycat(x,y)
  -- concat rows and append a sum
    return A(x,concat,y,concat,sum,x,plus,y)
  end
  function TestluAPL:testrank()
  -- rank - slice up arrays and pass as args to other functions
    local x = A(3,3,shape,1,plus,til,9)
    local y = A(3,3,shape,10)
    local z = A(x,arraycat,rank,1,1,y)
    assert(z,{ {1,2,3,10,10,10,36}, {4,5,6,10,10,10,45}, {7,8,9,10,10,10,54}})
  end

  function TestluAPL:testneg()
  -- unary minus
    local x = A(til,10)
    local y = A(sum,x,plus,neg,x)
    assert(y:value()==0)
    local z = A(sum,x,plus,-x)
    assert(z:value()==0)
  end

-- LuaUnit:run('TestLuaBinding:test_setline') -- will execute only one test
-- LuaUnit:run('TestLuaBinding') -- will execute only one class of test
-- LuaUnit.result.verbosity = 0
LuaUnit:run()

print("Press any key to exit")
io.read()
